﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Input;
using Animation;
using Input;
using MapLibrary.Players.Skills;
using MapLibrary.Players;
using Microsoft.Xna.Framework.Graphics;
using FarseerPhysics.Dynamics;
using MapLibrary.Players.Damage;
using Microsoft.Xna.Framework;
using FarseerPhysics.Dynamics.Contacts;
using MapLibrary.Players.CollisionResponse;
using UtilityLibrary;
using Microsoft.Xna.Framework.Content;
using MapLibrary.Players.Abilities;
using FarseerPhysics;
using MapLibrary.CollisionResponse;
using Enemies;
using GUILibrary;
using MapLibrary.Utility;
using MapLibrary.GUI;
using MapLibrary.GUI.Components;
using MapLibrary.Particles.Emitters;

namespace MapLibrary.Players
{
    using CollisionCategory = CollisionResponseManager.CollisionCategory;

    public class Player : Sprite, KeyboardInputListener, MouseInputListener, IDamageable, ITrackable, IParamTrackable
    {
        public static Texture2D healthBarTexture;
        public const int HEALTH_BAR_HEIGHT = 5;
        public const int HEALTH_BAR_WIDTH = 100;
        public const int MAX_NUM_PROTEIN = 999;

        private int numberJumps = 0;

        private float previousDamping;

        public int NumberJumps
        {
            get { return numberJumps; }
            set { numberJumps = value; }
        }
        public enum DamageCategories
        {
            Mucus,
            Physical,
            Water
        }

        private Dictionary<Keys, IActivatable> skillsAndAbilities;

        private List<Skill> skills;
        private List<Ability> abilities;
        private int[] proteinAmounts = new int[] { 0, 0, 0 };
        private int health;
        private int maxHealth;
        private int numLives;
        
        //private CollisionResponseManager collisionResponseManager;
        private Shooting shooting;

        public Player(GameWorld world)
        {
            skills = new List<Skill>();
            abilities = new List<Ability>();
            skillsAndAbilities = new Dictionary<Keys, IActivatable>();

            maxHealth = 100;
            health = maxHealth;
            numLives = 3;
            CollisionCategory = CollisionCategory.Player;
            //collisionResponseManager = new CollisionResponseManager();


            //build up skills and abilities list
            LoadSkills();
            LoadAbilities(world);
            InitCollisionData();

            MAX_VELOCITY_Y = ConvertUnits.ToSimUnits(1000);
            this.Controller.MaxLinearVelocityY = MAX_VELOCITY_Y;

            Hookable = false;
        }

        public List<Skill> Skill
        {
            get { return skills; }
        }

        public int[] ProteinAmounts
        {
            get { return proteinAmounts; }
        }

        public bool IsShieldActive()
        {
            PowerShield ps = skills[1] as PowerShield;
            return ps.Active;
        }

        public override void Update()
        {
            base.Update();

            //update the cooldowns on all of the skills
            foreach (Skill s in skills)
            {
                s.Update();
            }

            foreach (Ability a in abilities)
            {
                a.Update();
            }

            shooting.Update();

            PowerShield ps = skills[0] as PowerShield;
        }

        private void InitCollisionData()
        {
            CollisionAction tuberculosisResponse = new CollisionAction(CollisionCategory.Tuberculosis, false);
            tuberculosisResponse.Behaviors.Add(
                new SlowDownBehavior(this, 10.0f));
            tuberculosisResponse.Behaviors.Add(
                new DamageOverTimeBehavior(this, 10, 60));

            CollisionAction floorResponse = new CollisionAction(CollisionCategory.Floor, true);
            floorResponse.Behaviors.Add(
                new JumpUpdateBehavior(this));

            CollisionAction particleResponse = new CollisionAction(CollisionCategory.Particle, false);

            CollisionAction tuberculosisSpitResponse = new CollisionAction(CollisionCategory.TuberculosisSpit, false);

            collisionManager.AddCollisionAction(CollisionCategory.Tuberculosis, tuberculosisResponse);
            collisionManager.AddCollisionAction(CollisionCategory.Floor, floorResponse);
            collisionManager.AddCollisionAction(CollisionCategory.Particle, particleResponse);
            collisionManager.AddCollisionAction(CollisionCategory.TuberculosisSpit, tuberculosisSpitResponse);
        }

        public override bool MyOnCollision(Fixture f1, Fixture f2, Contact contact)
        {
            bool toReturn = base.MyOnCollision(f1, f2, contact);


            Sprite s2 = f2.Body.UserData as Sprite;

            if (s2 != null)
            {
                if (s2.SpriteTypeName.Equals("Net") || s2.SpriteTypeName.Equals("Malaria") || s2.SpriteTypeName.Equals("Bullet") || s2.SpriteTypeName.Equals("Hook"))
                {
                    return false;
                }

                if (s2.CollisionCategory == CollisionResponseManager.CollisionCategory.TuberculosisSpit)
                    return false;

                if (s2.CollisionCategory == CollisionResponseManager.CollisionCategory.Particle)
                {
                    ProteinParticle pParticle = s2 as ProteinParticle;

                    if (pParticle != null)
                    {
                        int type = (int)pParticle.TypeOfProtein;
                        int amount = pParticle.AmountOfProtein;
                        proteinAmounts[type] += amount;
                        if (proteinAmounts[type] > MAX_NUM_PROTEIN)
                            proteinAmounts[type] = MAX_NUM_PROTEIN;
                    }
                }
            }

            LevelCollisionRegion l1 = f2.Body.UserData as LevelCollisionRegion;
            if (l1 != null)
            {
                if (l1.Type.Equals("Mucus"))
                    return false;
            }

            return toReturn;
        }

        public override void Draw(SpriteBatch batch, WorldViewport viewport)
        {
            PowerShield ps = skills[1] as PowerShield;
            if (ps.Active)
            {
                base.Draw(batch, viewport, Color.LightSlateGray);
            }
            else
            {
                base.Draw(batch, viewport);
            }
            foreach (Skill s in skills)
            {
                //s.DrawAtPlayer(batch, viewport, this);
            }

            Vector2 position = viewport.WorldToViewportCoords(Position);
            position = position - GetCenter() - new Vector2(0, 50);

            //PrimitiveBrush.RenderText(batch, "Health: " + health, position);

            //Gliding g = abilities[2] as Gliding;
            //g.DrawAtPlayer(batch, viewport, this);


            //health bars
            //if (healthBarTexture == null)
            //{
            //    healthBarTexture = World.ContentManager.Load<Texture2D>(@"whitebox");
            //}

            //float percent = ((float)health) / ((float)maxHealth);
            //float width = HEALTH_BAR_WIDTH * percent;

            //position = this.GetPosition();
            //position.Y -= (CurrentTexture.Height / 2.0f) + 10;
            //position = viewport.WorldToViewportCoords(position);
            //position.X -= HEALTH_BAR_WIDTH / 2.0f;
            //PrimitiveBrush.RenderPoint(batch, position);

            //Rectangle rect = new Rectangle((int)position.X, (int)position.Y, HEALTH_BAR_WIDTH, HEALTH_BAR_HEIGHT);
            //batch.Draw(healthBarTexture, rect, Color.Black);
            //rect.Width = (int)width;

            //Color color;
            //if (percent >= .75f)
            //{
            //    color = Color.Green;
            //}
            //else if (percent > .25)
            //{
            //    color = Color.Yellow;
            //}
            //else
            //{
            //    color = Color.Red;
            //}
            //batch.Draw(healthBarTexture, rect, color);

            ////shield bar
            //percent = ps.ShieldLife / 100.0f;
            //width = HEALTH_BAR_WIDTH * percent;

            //position.Y += 5;
            //rect.Y = (int)position.Y;
            //rect.Width = HEALTH_BAR_WIDTH;
            //batch.Draw(healthBarTexture, rect, Color.Black);

            //rect.Width = (int)width;
            //batch.Draw(healthBarTexture, rect, Color.Silver);
        }

        public bool IsJumping()
        {
            return Type.CurrentState.Equals("Jumping Right")
                || Type.CurrentState.Equals("Jumping Left");
        }

        public bool IsRunning()
        {
            return Type.CurrentState.Equals("Running Right")
                || Type.CurrentState.Equals("Running Left");
        }

        public bool IsIdle()
        {
            return Type.CurrentState.Equals("Idle Right")
                || Type.CurrentState.Equals("Idle Left");
        }

        public bool IsLanding()
        {
            return Type.CurrentState.Equals("Landing Right")
                || Type.CurrentState.Equals("Landing Left");
        }

        public bool IsFacingRight()
        {
            return Type.CurrentState.Contains("Right");
        }

        public void EnterStateWithProperDirection(string baseStateName)
        {
            string direction;
            if (IsFacingRight())
                direction = "Right";
            else
                direction = "Left";

            string fullStateName = baseStateName + " " + direction;

            Type.CurrentState = fullStateName;
        }

        public void UpdateKeyboardInput(GameInput Input)
        {
            if (!GameStateManager.Instance.IsCurrentState(GameStateManager.GameState.InGame))
                return;

            if (Input.IsKeyDown(Keys.D))
            {
                if (IsLanding())
                {
                    if (Type.States[Type.CurrentState].IsStateComplete())
                    {
                        Body.ApplyForce(new Vector2(100, 0));
                        Type.CurrentState = "Running Right";
                    }
                }
                else
                {
                    if (!Type.CurrentState.Equals("Running Right") && !IsJumping())
                    {
                        Type.CurrentState = "Running Right";
                    }
                     
                    Body.ApplyForce(new Vector2(100, 0));
                }
            }
            else if (Input.IsKeyDown(Keys.A))
            {
                if (IsLanding())
                {
                    if (Type.States[Type.CurrentState].IsStateComplete())
                    {
                        Body.ApplyForce(new Vector2(-100, 0));
                        Type.CurrentState = "Running Left";
                    }
                }
                else
                {
                    if (!Type.CurrentState.Equals("Running Left") && !IsJumping())
                    {
                        Type.CurrentState = "Running Left";
                    }

                    Body.ApplyForce(new Vector2(-100, 0));
                }
            }
            else
            {
                if (!IsJumping())
                {
                    if (IsLanding())
                    {
                        if (Type.States[Type.CurrentState].IsStateComplete() && !IsIdle())
                            EnterStateWithProperDirection("Idle");
                    }
                    else
                    {
                        if(!IsIdle())
                            EnterStateWithProperDirection("Idle");
                    }
                }
            }

            foreach (Keys k in skillsAndAbilities.Keys)
            {
                if (Input.IsKeyPressed(k))
                {
                    if (skillsAndAbilities[k].ActivateConditionsSatisfied(this))
                        skillsAndAbilities[k].Activate(this);
                }
            }
            if (Input.IsKeyDown(Keys.Space))
            {
                if (abilities[2].ActivateConditionsSatisfied(this))
                {
                    abilities[2].Activate(this);
                }
            }
        }

        public void CancelHookShot()
        {
            HookShot hs = abilities[1] as HookShot;
            if (hs != null)
            {
                hs.Cancel();
            }
        }

        public void LoadFromData(PlayerData d)
        {
            int i = 0;
            foreach (SkillData sData in d.SkillData)
            {
                skills[i].LoadFromData(sData);
                i++;
            }

            i = 0;
            foreach (bool activate in d.ActivatedAbilities)
            {
                abilities[i].IsAvailable = activate;
                i++;
            }

            for (i = 0; i < 3; i++)
            {
                proteinAmounts[i] = d.ProteinAmounts[i].Value;
            }
        }

        public void DealDamage(int amount)
        {
            PowerShield shield = skills[1] as PowerShield;
            if (shield.Active)
            {
                shield.ShieldLife = shield.ShieldLife - amount;
                int remaining = shield.ShieldLife;
                if (shield.ShieldLife <= 0)
                {
                    shield.ShieldLife = 0;
                    health += remaining; //remaining is negative or zero.
                }
            }
            else
            {
                health -= amount;
            }

            if (health <= 0)
            {
                health = 0; //dead....TODO
                Die();
            }
        }

        public void Die()
        {
            numLives -= 1;
            if (numLives <= 0)
            {
                numLives = 0;  //game over...todo
                GameStateManager.Instance.SwitchState(
                    GameStateManager.GameState.GameOver);
            }

            health = maxHealth;
        }

        public PlayerData GetData()
        {
            PlayerData d = new PlayerData();
            d.SkillData = new List<SkillData>();
            d.ActivatedAbilities = new List<bool>();

            foreach (Skill s in skills)
            {
                d.SkillData.Add(s.GetData());
            }

            foreach (Ability a in abilities)
            {
                d.ActivatedAbilities.Add(a.IsAvailable);
            }

            for (int i = 0; i < 3; i++)
            {
                d.ProteinAmounts.Add(new IntWrapper(proteinAmounts[i]));
            }

            return d;
        }

        public void OnKeyboardRegister(GameInput Input)
        {
            //do nothing
        }

        public void UpdateMouseInput(GameInput Input)
        {
            if (Input.IsLeftMouseButtonPressed())
            {
                if (shooting.ActivateConditionsSatisfied(this))
                {
                    WorldViewport v = this.World.Viewport;
                    shooting.UpdateWithInput(v.ViewportCoordsToWorldCoords(
                        Input.GetMousePosition()));
                    shooting.Activate(this);
                }
            }
        }

        public void OnMouseRegister(GameInput Input)
        {
            //do nothing
        }

        public void AddProtein(int type, int amount)
        {
            this.proteinAmounts[type] += amount;
            int max = GetMax("PlayerProtein" + type);
            if(proteinAmounts[type] > max){
                proteinAmounts[type] = max;
            }
        }

        private void LoadSkills()
        {
            Skill s;

            //copy of net, needed temporarily.
            s = new Net();
            skills.Add(s);

            s = new PowerShield();
            skills.Add(s);
            skillsAndAbilities.Add(Keys.D2, s);

            s = new Explosion();
            skills.Add(s);
            skillsAndAbilities.Add(Keys.D3, s);

            s = new Net();
            skills.Add(s);
            skillsAndAbilities.Add(Keys.D1, s);
        }

        private void LoadAbilities(GameWorld context)
        {
            Ability a;

            a = new DoubleJump(this);
            a.IsAvailable = true;
            abilities.Add(a);
            skillsAndAbilities.Add(Keys.W, a);

            shooting = new Shooting(context);
            a.IsAvailable = true;

            a = new HookShot();
            abilities.Add(a);
            skillsAndAbilities.Add(Keys.E, a);
            a.IsAvailable = true;

            a = new Gliding(this);
            abilities.Add(a);
            skillsAndAbilities.Add(Keys.Space, a);
            a.IsAvailable = true;

            a = new Sliding(this);
            abilities.Add(a);
            skillsAndAbilities.Add(Keys.S, a);
            a.IsAvailable = true;
        }

        #region ITrackable
        
        public int GetAmount(string trackerName)
        {
            if (trackerName.Contains("ProteinBar"))
            {
                string mod = trackerName.Remove(0, 10);
                int barNum = int.Parse(mod);
                int proteinNum = barNum % 3;

                int skill = barNum / 3;

                return skills[skill].ProteinLevels[proteinNum];
            }
            else if (trackerName.Contains("PlayerProtein"))
            {
                string mod = trackerName.Remove(0, 13);
                int barNum = int.Parse(mod);

                return ProteinAmounts[barNum];
            }
            else if (trackerName.Contains("PlayerLife"))
            {
                return health;
            }
            else if (trackerName.Contains("PlayerLives"))
            {
                return numLives;
            }
            else
                return 0;
        }

        public int GetMax(string trackerName)
        {
            if (trackerName.Contains("ProteinBar"))
            {
                string mod = trackerName.Remove(0, 10);
                int barNum = int.Parse(mod);
                int proteinNum = barNum % 3;

                int skill = barNum / 3;

                return skills[skill].ProteinToLevel[proteinNum];
            }
            else if (trackerName.Contains("PlayerProtein"))
            {
                string mod = trackerName.Remove(0, 13);
                int barNum = int.Parse(mod);

                return 999;
            }
            else if (trackerName.Contains("PlayerLife"))
            {
                return maxHealth;
            }
            else
                return 0;
        }

        public void DecrimentSink(string trackerName)
        {
            if (trackerName.Contains("ProteinBar"))
            {
                string mod = trackerName.Remove(0, 10);
                int barNum = int.Parse(mod);
                int proteinNum = barNum % 3;

                ProteinAmounts[proteinNum] -= 1;
                if (ProteinAmounts[proteinNum] < 0)
                    ProteinAmounts[proteinNum] = 0;
            }
        }

        public void IncrimentValue(string trackerName)
        {
            if (trackerName.Contains("ProteinBar"))
            {
                string mod = trackerName.Remove(0, 10);
                int barNum = int.Parse(mod);
                int proteinNum = barNum % 3;

                int skill = barNum / 3;

                skills[skill].ProteinLevels[proteinNum]++;
                skills[skill].CheckAndExecuteLevelUp();
            }
            else if (trackerName.Contains("PlayerProtein"))
            {
                string mod = trackerName.Remove(0, 13);
                int barNum = int.Parse(mod);

                ProteinAmounts[barNum]++;
            }
        }

        public int GetSink(string trackerName)
        {
            if (trackerName.Contains("ProteinBar"))
            {
                string mod = trackerName.Remove(0, 10);
                int barNum = int.Parse(mod);
                int proteinNum = barNum % 3;

                return ProteinAmounts[proteinNum];
            }
            else
                return 0;
        }
        #endregion

        #region IParamTrackable
        public object[] GetParams(string trackerName)
        {
            if (trackerName.Contains("ProteinBarText"))
            {
                string mod = trackerName.Remove(0, 14);
                int barNum = int.Parse(mod);
                int proteinNum = barNum % 3;

                int skill = barNum / 3;

                object[] param = new object[] { skills[skill].ProteinLevels[proteinNum], skills[skill].ProteinToLevel[proteinNum] };
                return param;
            }
            else if (trackerName.Contains("PlayerProteinText"))
            {
                string mod = trackerName.Remove(0, 17);
                int barNum = int.Parse(mod);
                int proteinNum = barNum % 3;

                object[] param = new object[] { ProteinAmounts[proteinNum], 
                    999 };
                return param;
            }
            else if (trackerName.Contains("SkillLevel"))
            {
                string mod = trackerName.Remove(0, 10);
                int barNum = int.Parse(mod);

                object[] param = new object[] { skills[barNum].Level };
                return param;
            }
            else
                return null;
        }
        #endregion
    }
}
